// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE

// LUA mode. Ported to CodeMirror 2 from Franciszek Wawrzak's
// CodeMirror 1 mode.
// highlights keywords, strings, comments (no leveling supported! ("[==[")), tokens, basic indenting

(function(mod) {
  if (typeof exports == 'object' && typeof module == 'object')
    // CommonJS
    mod(require('../../lib/codemirror'));
  else if (typeof define == 'function' && define.amd)
    // AMD
    define(['../../lib/codemirror'], mod);
  // Plain browser env
  else mod(CodeMirror);
})(function(CodeMirror) {
  'use strict';

  CodeMirror.defineMode('lua', function(config, parserConfig) {
    var indentUnit = config.indentUnit;

    function prefixRE(words) {
      return new RegExp('^(?:' + words.join('|') + ')', 'i');
    }
    function wordRE(words) {
      return new RegExp('^(?:' + words.join('|') + ')$', 'i');
    }
    var specials = wordRE(parserConfig.specials || []);

    // long list of standard functions from lua manual
    var builtins = wordRE([
      '_G',
      '_VERSION',
      'assert',
      'collectgarbage',
      'dofile',
      'error',
      'getfenv',
      'getmetatable',
      'ipairs',
      'load',
      'loadfile',
      'loadstring',
      'module',
      'next',
      'pairs',
      'pcall',
      'print',
      'rawequal',
      'rawget',
      'rawset',
      'require',
      'select',
      'setfenv',
      'setmetatable',
      'tonumber',
      'tostring',
      'type',
      'unpack',
      'xpcall',

      'coroutine.create',
      'coroutine.resume',
      'coroutine.running',
      'coroutine.status',
      'coroutine.wrap',
      'coroutine.yield',

      'debug.debug',
      'debug.getfenv',
      'debug.gethook',
      'debug.getinfo',
      'debug.getlocal',
      'debug.getmetatable',
      'debug.getregistry',
      'debug.getupvalue',
      'debug.setfenv',
      'debug.sethook',
      'debug.setlocal',
      'debug.setmetatable',
      'debug.setupvalue',
      'debug.traceback',

      'close',
      'flush',
      'lines',
      'read',
      'seek',
      'setvbuf',
      'write',

      'io.close',
      'io.flush',
      'io.input',
      'io.lines',
      'io.open',
      'io.output',
      'io.popen',
      'io.read',
      'io.stderr',
      'io.stdin',
      'io.stdout',
      'io.tmpfile',
      'io.type',
      'io.write',

      'math.abs',
      'math.acos',
      'math.asin',
      'math.atan',
      'math.atan2',
      'math.ceil',
      'math.cos',
      'math.cosh',
      'math.deg',
      'math.exp',
      'math.floor',
      'math.fmod',
      'math.frexp',
      'math.huge',
      'math.ldexp',
      'math.log',
      'math.log10',
      'math.max',
      'math.min',
      'math.modf',
      'math.pi',
      'math.pow',
      'math.rad',
      'math.random',
      'math.randomseed',
      'math.sin',
      'math.sinh',
      'math.sqrt',
      'math.tan',
      'math.tanh',

      'os.clock',
      'os.date',
      'os.difftime',
      'os.execute',
      'os.exit',
      'os.getenv',
      'os.remove',
      'os.rename',
      'os.setlocale',
      'os.time',
      'os.tmpname',

      'package.cpath',
      'package.loaded',
      'package.loaders',
      'package.loadlib',
      'package.path',
      'package.preload',
      'package.seeall',

      'string.byte',
      'string.char',
      'string.dump',
      'string.find',
      'string.format',
      'string.gmatch',
      'string.gsub',
      'string.len',
      'string.lower',
      'string.match',
      'string.rep',
      'string.reverse',
      'string.sub',
      'string.upper',

      'table.concat',
      'table.insert',
      'table.maxn',
      'table.remove',
      'table.sort',
    ]);
    var keywords = wordRE([
      'and',
      'break',
      'elseif',
      'false',
      'nil',
      'not',
      'or',
      'return',
      'true',
      'function',
      'end',
      'if',
      'then',
      'else',
      'do',
      'while',
      'repeat',
      'until',
      'for',
      'in',
      'local',
    ]);

    var indentTokens = wordRE(['function', 'if', 'repeat', 'do', '\\(', '{']);
    var dedentTokens = wordRE(['end', 'until', '\\)', '}']);
    var dedentPartial = prefixRE([
      'end',
      'until',
      '\\)',
      '}',
      'else',
      'elseif',
    ]);

    function readBracket(stream) {
      var level = 0;
      while (stream.eat('=')) ++level;
      stream.eat('[');
      return level;
    }

    function normal(stream, state) {
      var ch = stream.next();
      if (ch == '-' && stream.eat('-')) {
        if (stream.eat('[') && stream.eat('['))
          return (state.cur = bracketed(readBracket(stream), 'comment'))(
            stream,
            state,
          );
        stream.skipToEnd();
        return 'comment';
      }
      if (ch == '"' || ch == "'")
        return (state.cur = string(ch))(stream, state);
      if (ch == '[' && /[\[=]/.test(stream.peek()))
        return (state.cur = bracketed(readBracket(stream), 'string'))(
          stream,
          state,
        );
      if (/\d/.test(ch)) {
        stream.eatWhile(/[\w.%]/);
        return 'number';
      }
      if (/[\w_]/.test(ch)) {
        stream.eatWhile(/[\w\\\-_.]/);
        return 'variable';
      }
      return null;
    }

    function bracketed(level, style) {
      return function(stream, state) {
        var curlev = null,
          ch;
        while ((ch = stream.next()) != null) {
          if (curlev == null) {
            if (ch == ']') curlev = 0;
          } else if (ch == '=') ++curlev;
          else if (ch == ']' && curlev == level) {
            state.cur = normal;
            break;
          } else curlev = null;
        }
        return style;
      };
    }

    function string(quote) {
      return function(stream, state) {
        var escaped = false,
          ch;
        while ((ch = stream.next()) != null) {
          if (ch == quote && !escaped) break;
          escaped = !escaped && ch == '\\';
        }
        if (!escaped) state.cur = normal;
        return 'string';
      };
    }

    return {
      startState: function(basecol) {
        return { basecol: basecol || 0, indentDepth: 0, cur: normal };
      },

      token: function(stream, state) {
        if (stream.eatSpace()) return null;
        var style = state.cur(stream, state);
        var word = stream.current();
        if (style == 'variable') {
          if (keywords.test(word)) style = 'keyword';
          else if (builtins.test(word)) style = 'builtin';
          else if (specials.test(word)) style = 'variable-2';
        }
        if (style != 'comment' && style != 'string') {
          if (indentTokens.test(word)) ++state.indentDepth;
          else if (dedentTokens.test(word)) --state.indentDepth;
        }
        return style;
      },

      indent: function(state, textAfter) {
        var closing = dedentPartial.test(textAfter);
        return (
          state.basecol + indentUnit * (state.indentDepth - (closing ? 1 : 0))
        );
      },

      lineComment: '--',
      blockCommentStart: '--[[',
      blockCommentEnd: ']]',
    };
  });

  CodeMirror.defineMIME('text/x-lua', 'lua');
});
